package org.blade.personal.dao.base.jpa;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.beanutils.PropertyUtils;
import org.blade.personal.component.DaoComponent;
import org.blade.personal.component.Order;
import org.blade.personal.component.OrderBy;
import org.blade.personal.component.UpdateType;
import org.blade.personal.dao.base.DaoImpl;
import org.blade.personal.mode.BaseEntity;
import org.blade.personal.utils.BeanUtils;
import org.blade.personal.utils.Pager;
import org.springframework.util.StringUtils;

/**
 * 
 * @author blade
 *
 * @param <T>
 * @param <ID>
 */
public class JpaDaoImpl<T extends BaseEntity<ID>, ID extends Serializable> extends DaoImpl<T, ID>
		implements JpaDao<T, ID> {

	
	@PersistenceContext
	protected EntityManager entityManager;

	private Class<T> entityClass;

	public JpaDaoImpl() {
		this.entityClass = null;
		Class c = getClass();
		Type t = c.getGenericSuperclass();
		if (t instanceof ParameterizedType) {
			Type[] p = ((ParameterizedType) t).getActualTypeArguments();
			this.entityClass = (Class<T>) p[0];
		}
	}

	public JpaDaoImpl(final Class<T> clazz) {
		this.entityClass = clazz;
	}

	public void setClazz(Class<T> clazz) {
		this.entityClass = clazz;
	}

	public Class<T> getClazz() {
		return this.entityClass;
	}

	public void update(T entity,UpdateType type){
		T t = getById(entity.getId());
		switch (type) {
		case NO_SKIP_NULL:
			BeanUtils.copyProperties(t, entity,false);
			entityManager.merge(t);
			break;
		case SKIP_NULL:
			BeanUtils.copyProperties(t, entity,true);
			entityManager.merge(t);
			break;
		case NONE:
			entityManager.merge(entity);
			break;
		default:
			break;
		}
	}
	
	public void updateList(List<T> entityList) {
		for (T t : entityList) {
			update(t);
		}
	}
	
	public void updateList(List<T> entityList,UpdateType type){
		for (T t : entityList) {
			update(t,type);
		}
	}

	@Override
	public  void add(T entity) {
		entityManager.persist(entity);
	}

	@Override
	public void add(List<T> entities) {

		for (T entity : entities) {
			this.add(entity);
		}

	}

	@Override
	public void update(T entity) {
		entityManager.merge(entity);
	}

	@Override
	public void update(List<T> entities) {
		for (T entity : entities) {
			this.update(entity);
		}
	}

	@Override
	public void delete(T entity) {
		entityManager.remove(getById(entity.getId()));
	}

	@Override
	public void deleteById(ID id) {
		entityManager.remove(getById(id));
	}

	@Override
	public void delete(List<T> entities) {
		for (T entity : entities) {
			this.delete(entity);
		}
	}

	@Override
	public List<T> queryForList(T entity) {
		return this.findList(entity, null, null);
	}

	@Override
	public T getById(ID id) {
		return entityManager.find(entityClass, id);
	}

	@Override
	public List<T> queryForList() {
		return null;
	}

	@Override
	public void pagination(Pager<T> pager, T entity) {

		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<T> cq = cb.createQuery(entityClass);
		Root root = cq.from(entityClass);
		List<T> entities = findList(new DaoComponent<T, ID>(cb, cq, root), entity, pager, null);
		pager.setEntities(entities);
	}

	@Override
	public Pager<T> pagination(int pageSize, int pageNum, T entity) {

		Pager<T> pager = new Pager<T>((long) pageSize, (long) pageNum);
		this.pagination(pager, entity);
		return pager;
	}

	@Override
	public Pager<T> pagination(String sql, int pageSize, int pageNum, Map<String, Object> param) {
		// TODO Auto-generated method stub
		Pager<T> pager = new Pager<T>((long) pageSize, (long) pageNum);

		Query query =entityManager.createNativeQuery(sql, entityClass);
		if(null!=param){
			for (Map.Entry<String, Object> entry : param.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
		}
		if(null!=pager){
			query.setFirstResult(pager.getIndex().intValue()).setMaxResults(pager.getPageSize().intValue());
		}
		
		pager.setEntities(query.getResultList());
		return pager;
	}

	@Override
	public List pagination(String sql, Pager<T> pager, Map<String, Object> param) {
		
		Query query =entityManager.createNativeQuery(sql, entityClass);
		if(null!=param){
			for (Map.Entry<String, Object> entry : param.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
		}
		if(null!=pager){
			query.setFirstResult(pager.getIndex().intValue()).setMaxResults(pager.getPageSize().intValue());
		}
		return query.getResultList();
	}

	@Override
	public List<T> findList() {
		return this.findList(null, null, null);

	}

	@Override
	public List<T> findList(OrderBy... order) {
		return this.findList(null, null, order);

	}

	@Override
	public List<T> findList(T t, Pager<T> pager) {
		return this.findList(t, pager, null);
	}

	@Override
	public List<T> findList(T t, OrderBy... order) {
		return this.findList(t, null, order);
	}

	@Override
	public List<T> findList(Pager<T> pager, OrderBy... order) {
		return this.findList(null, pager, order);
	}

	@Override
	public List<T> findList(T t, Pager<T> pager, OrderBy... order) {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery cq = cb.createQuery(entityClass);
		Root root = cq.from(entityClass);
		return findList(new DaoComponent<T, ID>(cb, cq, root), t, pager, order);
	}

	@Override
	public List<T> findListBySql(String sql, Pager<T> pager, Object... param) {

		String countSql = "select count(1) (" + sql + ") ";
		Query query = entityManager.createNativeQuery(countSql);
		for (int i = 0; i < param.length; i++) {
			query.setParameter(i + 1, param[i]);
		}
		Long totalCount = (Long) query.getSingleResult();
		pager.setTotalCount(totalCount);
		if (totalCount > 0) {
			query = entityManager.createNativeQuery(sql);
			for (int i = 0; i < param.length; i++) {
				query.setParameter(i + 1, param[i]);
			}
			pager.setEntities(query.getResultList());
		}
		return pager.getEntities();
	}

	/**
	 * 
	 * @param db
	 * @param t
	 * @param page
	 * @param order
	 * @return
	 */
	private List<T> findList(DaoComponent<T, ID> dc, T t, Pager<T> page, OrderBy... order) {
		CriteriaBuilder cb = dc.getCriteriaBuilder();
		Root root = dc.getRoot();
		if (null != t) {
			// 获取所有src中的属性，存入于数组中
			PropertyDescriptor[] origDescriptors = PropertyUtils.getPropertyDescriptors(t);
			List<Predicate> param = new ArrayList<Predicate>();
			for (int i = 0; i < origDescriptors.length; i++) {
				// 取出src中属性名
				String name = origDescriptors[i].getName();
				if ("class".equals(name)) {
					continue; // No point in trying to set an object's class
				}
				if (PropertyUtils.isReadable(t, name)) {// 实体可读
					Object value = null;
					try {
						// 取出属性的值
						Class<?> type = PropertyUtils.getPropertyType(t, name);
						value = PropertyUtils.getSimpleProperty(t, name);
						if (StringUtils.isEmpty(value)) {
							continue;
						} else {
							Path s = root.get(name);
							if (type == String.class && t.useQueryStringLike) {
								Predicate p1 = cb.and(cb.like(s, "%".concat(value.toString()).concat("%")));
								param.add(p1);
							} else {
								Predicate p1 = cb.and(cb.equal(s, value));
								param.add(p1);
							}
						}
					} catch (Exception e) {
						throw new RuntimeException(e);
					}
				}
			}
			return findPageList(dc, param, page, order);// 有条件查询
		} else {
			return findPageList(dc, null, page, order);// 无条件查询
		}
	}

	private List<T> findPageList(DaoComponent<T, ID> db, List<Predicate> pList, Pager<T> pager, OrderBy... order) {
		CriteriaBuilder cb = db.getCriteriaBuilder();
		CriteriaQuery<T> cq = db.getCriteriaQuery();
		Root root = db.getRoot();
		if (null != pList && pList.size() != 0) {
			cq.where(pList.toArray(new Predicate[0]));
		}
		List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
		for (OrderBy od : order) {
			if (od.getOrder().equals(Order.DESC)) {
				orderList.add(cb.desc(root.get(od.getFieldName())));
			} else {
				orderList.add(cb.asc(root.get(od.getFieldName())));
			}
		}
		cq.orderBy(orderList);
		cq.select(root);
		Query query = entityManager.createQuery(cq);
		if (null != pager) {
			Long totalCount = getPageListCount(db, pList);
			pager.setTotalCount(totalCount);
			query.setFirstResult(pager.getIndex().intValue()).setMaxResults(pager.getPageSize().intValue());
		}
		List<T> entities = query.getResultList();
		pager.setEntities(entities);
		return entities;
	}

	private Long getPageListCount(DaoComponent<T, ID> db, List<Predicate> pList) {
		CriteriaBuilder critBuilder = db.getCriteriaBuilder();
		CriteriaQuery<Long> critQuery = critBuilder.createQuery(Long.class);
		Root<T> root = critQuery.from(entityClass);
		if (null != pList && pList.size() != 0) {
			critQuery.where(pList.toArray(new Predicate[0]));
		}
		critQuery.select(critBuilder.count(root));
		Long count = entityManager.createQuery(critQuery).getSingleResult();
		return count;
	}
	
	public List<T> findListByMapParamSql(String sql, Pager<T> page, Map<String, Object> param) {
		Query query =entityManager.createNativeQuery(sql, entityClass);
		if(null!=param){
			for (Map.Entry<String, Object> entry : param.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
		}
		if(null!=page){
			query.setFirstResult(page.getIndex().intValue())
			.setMaxResults(page.getPageSize().intValue());
		}
		return query.getResultList();
	}
	
	public Integer findCountBySql(String sql, Object... param) {
		Query query =entityManager.createNativeQuery(sql);
		for (int i = 0; i < param.length; i++) {
			query.setParameter(i+1, param[i]);
		}
		return Integer.valueOf(query.getSingleResult().toString());
	}

	public Integer findCountBySql(String sql, Map<String, Object> param) {
		Query query =entityManager.createNativeQuery(sql);
		if(null!=param){
			for (Map.Entry<String, Object> entry : param.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
		}
		return Integer.valueOf(query.getSingleResult().toString());
	}

	@Override
	public int executeSql(String sql) {
		return entityManager.createNativeQuery(sql).executeUpdate();
	}

	@Override
	public int executeSql(String sql, Object... param) {
		Query query =entityManager.createNativeQuery(sql);
		for (int i = 0; i < param.length; i++) {
			query.setParameter(i+1, param[i]);
		}
		return query.executeUpdate();
	}

	@Override
	public int executeSql(String sql, Map<String, Object> param) {
		Query query =entityManager.createNativeQuery(sql);
		if(null!=param){
			for (Map.Entry<String, Object> entry : param.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
		}
		return query.executeUpdate();
	}

	public EntityManager getEntityManager() {
		return entityManager;
	}
	
	


	public List findObjectListBySql(String sql) {
		Query query =entityManager.createNativeQuery(sql);
		return query.getResultList();
	}

	@Override
	public List<T> findObjectListBySql(String sql, Pager pager) {
		Query query = entityManager.createNativeQuery(sql);
		query.setFirstResult(pager.getIndex().intValue())
		.setMaxResults(pager.getPageSize().intValue());
		return (List<T>)query.getResultList();
	}
	
	@Override
	public List findObjectListBySql(String sql, Pager page, Object... param) {
		Query query =entityManager.createNativeQuery(sql);
		for (int i = 0; i < param.length; i++) {
			query.setParameter(i+1, param[i]);
		}
		if(null!=page){
			query.setFirstResult(page.getIndex().intValue())
			.setMaxResults(page.getPageSize().intValue());
		}
		return query.getResultList();
	}

	public List findObjectListBySql(String sql, Pager page, Map<String, Object> param) {
		Query query =entityManager.createNativeQuery(sql);
		if(null!=param){
			for (Map.Entry<String, Object> entry : param.entrySet()) {
				query.setParameter(entry.getKey(), entry.getValue());
			}
		}
		if(null!=page){
			query.setFirstResult(page.getIndex().intValue())
			.setMaxResults(page.getPageSize().intValue());
		}
		return query.getResultList();
	}

}
